#include "oled.h"

static uint8_t _oled_sram[OLED_ROWS / 8][OLED_COLS] = {0}; // 图片缓存

/**
 * @name 写入字节
 * @description
 * @function
 * @param {uint8_t} data 数据
 * @param {OLED_MODE} cmd 命令
 * @returns {void}
 * @requires [data,cmd]
 */
static void _oled_writeByte(uint8_t data, const OLED_MODE cmd) {
    uint8_t i;
    // 命令数据选择
    if (cmd)
        OLED_DC_Set();
    else
        OLED_DC_Clr();
    // 拉低片选信号
    OLED_CS_Clr();
    for (i = 0; i < 8; data <<= 1, i += 1) {
        // 拉低时钟信号
        OLED_SCK_Clr();
        // 发送数据信息
        if (data & 0x80)
            OLED_SDA_Set();
        else
            OLED_SDA_Clr();
        // 拉高时钟信号
        OLED_SCK_Set();
    }
    // 拉高片选信号
    OLED_CS_Set();
    // 选择数据模式
    OLED_DC_Set();
}

/**
 * @name oled初始化
 * @description
 * @function
 * @returns {void}
 * @requires []
 */
void oled_init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    // 使能时钟
    OLED_RCC_AHBPeriphClockCmd;
    // 配置GPIO
    GPIO_InitStruct.GPIO_Pin = OLED_GPIO_Pin;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
    // 初始GPIO
    GPIO_Init(OLED_GPIO, &GPIO_InitStruct);
    // 使能OLED
    OLED_RES_Clr();
    delay_ms(200);
    OLED_RES_Set();
    // 关闭显示
    _oled_writeByte(0xae, OLED_CMD);
    // 低列地址
    _oled_writeByte(0x02, OLED_CMD);
    // 高列地址
    _oled_writeByte(0x10, OLED_CMD);
    // 显示行号
    _oled_writeByte(0x40, OLED_CMD);
    // 行号地址
    _oled_writeByte(0xb0, OLED_CMD);
    // 对比信息
    _oled_writeByte(0x81, OLED_CMD);
    _oled_writeByte(0xcf, OLED_CMD);
    // 段号地址
    _oled_writeByte(0xa1, OLED_CMD);
    // 普通反转
    _oled_writeByte(0xa6, OLED_CMD);
    // 复用比率
    _oled_writeByte(0xa8, OLED_CMD);
    _oled_writeByte(0x3f, OLED_CMD);
    // 启用电泵
    _oled_writeByte(0xad, OLED_CMD);
    _oled_writeByte(0x8b, OLED_CMD);
    // VCC电压
    _oled_writeByte(0x33, OLED_CMD);
    // 扫描方向
    _oled_writeByte(0xc8, OLED_CMD);
    // 显示偏移
    _oled_writeByte(0xd3, OLED_CMD);
    _oled_writeByte(0x00, OLED_CMD);
    // 时钟频率
    _oled_writeByte(0xd5, OLED_CMD);
    _oled_writeByte(0x80, OLED_CMD);
    // 预充电期
    _oled_writeByte(0xd9, OLED_CMD);
    _oled_writeByte(0x1f, OLED_CMD);
    // COM引脚
    _oled_writeByte(0xda, OLED_CMD);
    _oled_writeByte(0x12, OLED_CMD);
    // 电压等级
    _oled_writeByte(0xdb, OLED_CMD);
    _oled_writeByte(0x40, OLED_CMD);
    // 清空屏幕
    oled_clear();
    // 开启显示
    _oled_writeByte(0xaf, OLED_CMD);
}

/**
 * @name oled设置颜色
 * @description
 * @function
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [color]
 */
void oled_setColor(const OLED_COLOR color) {
    // 选择颜色
    switch (color) {
    case OLED_COLOR_BLUE:
        _oled_writeByte(0xa6, OLED_CMD);
        break;
    case OLED_COLOR_BLACK:
        _oled_writeByte(0xa7, OLED_CMD);
        break;
    default:
        break;
    }
}

/**
 * @name oled设置旋转角度
 * @description
 * @function
 * @param {ROTATE} rotate 旋转角度
 * @returns {void}
 * @requires [rotate]
 */
void oled_displayRotate(const ROTATE rotate) {
    // 旋转角度
    switch (rotate) {
    case OLED_ROTATE_0:
        _oled_writeByte(0xc8, OLED_CMD);
        _oled_writeByte(0xa1, OLED_CMD);
        break;
    case OLED_ROTATE_180:
        _oled_writeByte(0xc0, OLED_CMD);
        _oled_writeByte(0xa0, OLED_CMD);
        break;
    default:
        break;
    }
}

/**
 * @name oled刷新
 * @description
 * @function
 * @returns {void}
 * @requires []
 */
void oled_refresh(void) {
    uint8_t row, col;
    // 单行发送数据
    for (row = 0; row < OLED_ROWS / 8; ++row) {
        // 行号起始地址
        _oled_writeByte(0xb0 + row, OLED_CMD);
        // 低列起始地址
        _oled_writeByte(0x02, OLED_CMD);
        // 高列起始地址
        _oled_writeByte(0x10, OLED_CMD);
        // 发送数据信息
        for (col = 0; col < OLED_COLS; ++col)
            _oled_writeByte(_oled_sram[row][col], OLED_DATA);
    }
}

/**
 * @name oled清屏
 * @description
 * @function
 * @returns {void}
 * @requires []
 */
void oled_clear(void) {
    uint8_t row, col;
    // 清空缓存
    for (row = 0; row < OLED_ROWS / 8; ++row)
        for (col = 0; col < OLED_COLS; ++col)
            _oled_sram[row][col] = 0;
}

/**
 * @name oled填充
 * @description
 * @function
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [color]
 */
void oled_fill(const OLED_COLOR color) {
    uint8_t row, col;
    const static uint8_t colors[] = {0xff, 0x00};
    // 填充颜色
    for (row = 0; row < OLED_ROWS / 8; ++row)
        for (col = 0; col < OLED_COLS; ++col)
            _oled_sram[row][col] = colors[color];
}

/**
 * @name 绘制点号
 * @description
 * @function
 * @param {uint8_t} x x坐标
 * @param {uint8_t} y y坐标
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x,y,color]
 */
void oled_drawPoint(const uint8_t x, const uint8_t y, const OLED_COLOR color) {
    uint8_t row;
    // 坐标越界
    if (x > OLED_COLS || y > OLED_ROWS)
        return;
    // 计算行号
    row = y / 8;
    // 绘制点号
    switch (color) {
    case OLED_COLOR_BLUE:
        _oled_sram[row][x] |= 1 << (y % 8);
        break;
    case OLED_COLOR_BLACK:
        _oled_sram[row][x] &= ~(1 << (y % 8));
        break;
    default:
        break;
    }
}

/**
 * @name 绘制线段
 * @description
 * @function
 * @param {uint8_t} x1 x1坐标
 * @param {uint8_t} y1 y1坐标
 * @param {uint8_t} x2 x2坐标
 * @param {uint8_t} y2 y2坐标
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x1,y1,x2,y2,color]
 */
void oled_drawLine(const uint8_t x1, const uint8_t y1, const uint8_t x2, const uint8_t y2, const OLED_COLOR color) {
    int16_t i, xerr = 0, yerr = 0, ux = x1, uy = y1, incx, incy, delta_x, delta_y, distance;
    // 水平增量
    delta_x = (int16_t)x2 - (int16_t)x1, delta_y = (int16_t)y2 - (int16_t)y1;
    if (delta_x > 0)
        incx = 1;
    else if (delta_x == 0)
        incx = 0;
    else
        incx = -1, delta_x = -delta_x;
    // 垂直增量
    if (delta_y > 0)
        incy = 1;
    else if (delta_y == 0)
        incy = 0;
    else
        incy = -1, delta_y = -delta_x;
    // 增量坐标
    distance = delta_x > delta_y ? delta_x : delta_y;
    // 绘制线段
    for (i = 0; i <= distance; ++i) {
        // 绘制点号
        oled_drawPoint(ux, uy, color);
        // 下个坐标
        xerr += delta_x, yerr += delta_y;
        if (xerr > distance)
            xerr -= distance, ux += incx;
        if (yerr > distance)
            yerr -= distance, uy += incy;
    }
}

/**
 * @name 绘制矩形
 * @description
 * @function
 * @param {uint8_t} x x坐标
 * @param {uint8_t} y y坐标
 * @param {uint8_t} w 宽度
 * @param {uint8_t} h 高度
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x,y,w,h,color]
 */
void oled_drawRect(const uint8_t x, const uint8_t y, const uint8_t w, const uint8_t h, const OLED_COLOR color) {
    // 绘制矩形
    oled_drawLine(x, y, x + w, y, color);
    oled_drawLine(x, y + h, x + w, y + h, color);
    oled_drawLine(x, y, x, y + h, color);
    oled_drawLine(x + w, y, x + w, y + h, color);
}

/**
 * @name 绘制圆形
 * @description
 * @function
 * @param {uint8_t} x0 x坐标
 * @param {uint8_t} y0 y坐标
 * @param {uint8_t} r 半径
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x,y,r,color]
 */
void oled_drawCircle(const uint8_t x0, const uint8_t y0, const uint8_t r, const OLED_COLOR color) {
    int32_t x = 0, y = r, p = 3 - 2 * r;
    // Bresenham画圆算法
    while (x < y) {
        oled_drawPoint((int32_t)x0 + x, (int32_t)y0 - y, color);
        oled_drawPoint((int32_t)x0 - x, (int32_t)y0 - y, color);
        oled_drawPoint((int32_t)x0 - x, (int32_t)y0 + y, color);
        oled_drawPoint((int32_t)x0 + x, (int32_t)y0 + y, color);
        oled_drawPoint((int32_t)x0 + y, (int32_t)y0 + x, color);
        oled_drawPoint((int32_t)x0 + y, (int32_t)y0 - x, color);
        oled_drawPoint((int32_t)x0 - y, (int32_t)y0 - x, color);
        oled_drawPoint((int32_t)x0 - y, (int32_t)y0 + x, color);
        if (p < 0)
            p = p + 4 * x + 6;
        else
            p = p + 4 * (x - y) + 10, y -= 1;
        x += 1;
    }
    if (x == y) {
        oled_drawPoint((int32_t)x0 + x, (int32_t)y0 - y, color);
        oled_drawPoint((int32_t)x0 - x, (int32_t)y0 - y, color);
        oled_drawPoint((int32_t)x0 - x, (int32_t)y0 + y, color);
        oled_drawPoint((int32_t)x0 + x, (int32_t)y0 + y, color);
        oled_drawPoint((int32_t)x0 + y, (int32_t)y0 + x, color);
        oled_drawPoint((int32_t)x0 + y, (int32_t)y0 - x, color);
        oled_drawPoint((int32_t)x0 - y, (int32_t)y0 - x, color);
        oled_drawPoint((int32_t)x0 - y, (int32_t)y0 + x, color);
    }
}

/**
 * @name 绘制字符
 * @description
 * @function
 * @param {uint8_t} x x坐标
 * @param {uint8_t} y y坐标
 * @param {uint8_t} ch 字符
 * @param {OLED_ASCIIFONTSIZE} size 大小
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x,y,ch,size,color]
 */
void oled_showASCII(uint8_t x, uint8_t y, const uint8_t ch, const OLED_ASCIIFONTSIZE size, const OLED_COLOR color) {
    uint8_t *font, x0 = x, y0 = y, i, k, byte, length;
    // 读字节数
    length = size == OLED_ASCIIFONTSIZE_8_0806 ? 6 : (size / 8 + (size % 8 ? 1 : 0)) * (size / 2);
    // 读取字库
    switch (size) {
    case OLED_ASCIIFONTSIZE_8_0806:
        if (ch < ' ' || ch - ' ' >= sizeof(ASCII_0806) / size)
            return;
        font = (uint8_t *)ASCII_0806[ch - ' '];
        break;
    case OLED_ASCIIFONTSIZE_12_1206:
        if (ch < ' ' || ch - ' ' >= sizeof(ASCII_1206) / size)
            return;
        font = (uint8_t *)ASCII_1206[ch - ' '];
        break;
    case OLED_ASCIIFONTSIZE_16_1608:
        if (ch < ' ' || ch - ' ' >= sizeof(ASCII_1608) / size)
            return;
        font = (uint8_t *)ASCII_1608[ch - ' '];
        break;
    case OLED_ASCIIFONTSIZE_24_2412:
        if (ch < ' ' || ch - ' ' >= sizeof(ASCII_2412) / size)
            return;
        font = (uint8_t *)ASCII_2412[ch - ' '];
        break;
    default:
        return;
    }
    // 绘制文字
    for (i = 0; i < length; y = y0, ++i) {
        byte = font[i];
        for (k = 0; k < 8; byte >>= 1, ++k, ++y)
            oled_drawPoint(x, y, byte & 0x01 ? color : OLED_COLOR_BLACK);
        ++x;
        if (size != OLED_ASCIIFONTSIZE_8_0806 && x - x0 == size / 2)
            x = x0, y0 = y0 + 8;
    }
}

/**
 * @name 绘制汉字
 * @description
 * @function
 * @param {uint8_t} x x坐标
 * @param {uint8_t} y y坐标
 * @param {uint8_t} index 汉字索引
 * @param {OLED_HZKFONTSIZE} size 大小
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x,y,index,size,color]
 */
void oled_showHZK(uint8_t x, uint8_t y, const uint8_t index, const OLED_HZKFONTSIZE size, const OLED_COLOR color) {
    uint8_t *font, byte, x0 = x, y0 = y;
    uint16_t i, k, length = (size / 8 + (size % 8 ? 1 : 0)) * size;
    // 读取字库
    switch (size) {
    case OLED_HZKFONTSIZE_16_1616:
        if (index >= sizeof(HZK_1616) / size)
            return;
        font = (uint8_t *)HZK_1616[index];
        break;
    case OLED_HZKFONTSIZE_24_2424:
        if (index >= sizeof(HZK_2424) / size)
            return;
        font = (uint8_t *)HZK_2424[index];
        break;
    case OLED_HZKFONTSIZE_32_3232:
        if (index >= sizeof(HZK_3232) / size)
            return;
        font = (uint8_t *)HZK_3232[index];
        break;
    default:
        return;
    }
    // 绘制文字
    for (i = 0; i < length; y = y0, ++i) {
        byte = font[i];
        for (k = 0; k < 8; byte >>= 1, ++k, ++y)
            oled_drawPoint(x, y, byte & 0x01 ? color : OLED_COLOR_BLACK);
        ++x;
        if (x - x0 == size)
            x = x0, y0 = y0 + 8;
    }
}

/**
 * @name 绘制数字
 * @description
 * @function
 * @param {uint8_t} x x坐标
 * @param {uint8_t} y y坐标
 * @param {double} number 数字
 * @param {uint8_t} digit 位数
 * @param {OLED_ASCIIFONTSIZE} size 大小
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x,y,number,digit,size,color]
 */
void oled_showNumber(uint8_t x, uint8_t y, const double number, const uint8_t digit, const OLED_ASCIIFONTSIZE size, const OLED_COLOR color) {
    uint8_t i, length, x0806 = size == OLED_ASCIIFONTSIZE_8_0806 ? 2 : 0, numberString[18];
    // 转字符串
    sprintf((char *)numberString, "%.*f", digit, number);
    // 计算长度
    length = strlen((char *)numberString);
    // 绘制数字
    for (i = 0; i < length; ++i, x += size / 2 + x0806)
        oled_showASCII(x, y, numberString[i], size, color);
}

/**
 * @name 绘制ASCII字符串
 * @description
 * @function
 * @param {uint8_t} x x坐标
 * @param {uint8_t} y y坐标
 * @param {uint8_t} *str ASCII字符串
 * @param {OLED_ASCIIFONTSIZE} size 大小
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x,y,str,size,color]
 */
void oled_showASCIIString(uint8_t x, uint8_t y, const uint8_t *str, const OLED_ASCIIFONTSIZE size, const OLED_COLOR color) {
    uint8_t *pch = (uint8_t *)str;
    // 显示字符
    while (*pch >= ' ' && *pch <= '~') {
        oled_showASCII(x, y, *pch, size, color);
        x += size == OLED_ASCIIFONTSIZE_8_0806 ? 6 : size / 2;
        ++pch;
    }
}

/**
 * @name 绘制HZK字符串
 * @description
 * @function
 * @param {uint8_t} x x坐标
 * @param {uint8_t} y y坐标
 * @param {int32_t} *str HZK字符串(索引列表; -1结束)
 * @param {OLED_HZKFONTSIZE} size 大小
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x,y,str,size,color]
 */
void oled_showHZKString(uint8_t x, uint8_t y, const int32_t *str, const OLED_HZKFONTSIZE size, const OLED_COLOR color) {
    int32_t *pch = (int32_t *)str;
    // 显示字符
    while (*pch >= 0) {
        oled_showHZK(x, y, *pch, size, color);
        x += size;
        ++pch;
    }
}

/**
 * @name 绘制图片
 * @description
 * @function
 * @param {uint8_t} x x坐标
 * @param {uint8_t} y y坐标
 * @param {uint8_t} width 宽度
 * @param {uint8_t} height 高度
 * @param {uint8_t} *bmp 图片
 * @param {OLED_COLOR} color 颜色
 * @returns {void}
 * @requires [x,y,width,height,bmp,color]
 */
void oled_showPicture(uint8_t x, uint8_t y, uint8_t width, uint8_t height, const uint8_t *bmp, const OLED_COLOR color) {
    uint16_t index = 0;
    uint8_t k, row, col, byte, x0 = x, y0 = y;
    // 计算高度
    height = height / 8 + (height % 8 ? 1 : 0);
    // 绘制图片
    for (row = 0; row < height; ++row) {
        for (col = 0; col < width; ++col, y = y0) {
            byte = bmp[index++];
            for (k = 0; k < 8; byte >>= 1, ++k, ++y)
                oled_drawPoint(x, y, byte & 0x01 ? color : OLED_COLOR_BLACK);
            ++x;
            if (x - x0 == width)
                x = x0, y0 += 8;
        }
    }
}
